home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / bin / openssl-vulnkey < prev    next >
Text File  |  2008-06-19  |  6KB  |  205 lines

  1. #!/usr/bin/python
  2. #
  3. #    openssl-vulnkey: check a database of sha1'd static key hashes for
  4. #      known vulnerable keys
  5. #    Copyright (C) 2008 Canonical Ltd.
  6. #    Author: Jamie Strandboge <jamie@canonical.com>
  7. #
  8. #    This program is free software: you can redistribute it and/or modify
  9. #    it under the terms of the GNU General Public License version 2,
  10. #    as published by the Free Software Foundation.
  11. #
  12. #    This program is distributed in the hope that it will be useful,
  13. #    but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15. #    GNU General Public License for more details.
  16. #
  17. #    You should have received a copy of the GNU General Public License
  18. #    along with this program.  If not, see <http://www.gnu.org/licenses/>.
  19. #
  20.  
  21. from optparse import OptionParser
  22. import os
  23. import re
  24. import sha
  25. import subprocess
  26. import sys
  27. import tempfile
  28. import shutil
  29.  
  30. version = "0.3.3"
  31. db_prefix = "/usr/share/openssl-blacklist/blacklist.RSA-"
  32. db_lines = []
  33.  
  34. parser = OptionParser(usage="%prog FILE [FILE]", \
  35.                       version="%prog: " + version, \
  36.                       description="This program checks if FILEs are known " + \
  37.                                   "vulnerable static keys")
  38. parser.add_option("-q", "--quiet", action="store_true", dest="quiet", \
  39.                   help="be quiet")
  40. parser.add_option("-b", "--bits", dest="bits", \
  41.                   help="number of bits (requires -m)")
  42. parser.add_option("-m", "--modulus", dest="modulus", \
  43.                   help="modulus to check (requires -b)")
  44. (options, args) = parser.parse_args()
  45.  
  46. if not ((options.modulus and options.bits) or args):
  47.     parser.print_help()
  48.     sys.exit(1)
  49.  
  50. def cmd(command, input = None, stderr = subprocess.PIPE, stdout = subprocess.PIPE, stdin = None):
  51.     '''Try to execute given command (array) and return its stdout, or return
  52.     a textual error if it failed.'''
  53.  
  54.     try:
  55.        sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True)
  56.     except OSError, e:
  57.         return [127, str(e)]
  58.  
  59.     out = sp.communicate(input)[0]
  60.     return [sp.returncode,out]
  61.  
  62. def get_contents(file):
  63.     '''Determine the type of certificate it is. Returns empty string if
  64.        unsupported.'''
  65.     args = ['-modulus', '-text', '-in', file]
  66.  
  67.     rc, report = cmd(['openssl', 'rsa'] + args)
  68.     if rc == 0:
  69.         return ("rsa", report)
  70.  
  71.     rc, report = cmd(['openssl', 'x509'] + args)
  72.     if rc == 0:
  73.         return ("x509", report)
  74.  
  75.     rc, report = cmd(['openssl', 'req'] + args)
  76.     if rc == 0:
  77.         return ("req", report)
  78.  
  79.     return ("", report)
  80.  
  81. def get_bits(contents, type):
  82.     '''Find bit length of file. Returns empty string if unsupported.'''
  83.     for line in contents:
  84.         leading = "Private-Key: "
  85.         if type == "x509" or type == "req":
  86.             leading = "RSA Public Key: "
  87.              
  88.         # TODO: don't hardcode these
  89.         if leading + "(512" in contents:
  90.             return "512"
  91.         elif leading + "(1024" in contents:
  92.             return "1024"
  93.         elif leading + "(2048" in contents:
  94.             return "2048"
  95.         elif leading + "(4096" in contents:
  96.             return "4096"
  97.         elif leading + "(8192" in contents:
  98.             return "8192"
  99.  
  100.     return ""
  101.  
  102. def get_modulus(contents):
  103.     '''Find modulus of file'''
  104.     for line in contents.split('\n'):
  105.         if re.match(r'^Modulus=', line):
  106.             return line + '\n'
  107.  
  108.     return ""
  109.  
  110. def get_exponent(contents):
  111.     '''Find exponent of file. Returns empty string if unsupported.'''
  112.     if "Exponent: 65537 " in contents:
  113.         return "65537"
  114.  
  115.     return ""
  116.  
  117. def check_db(bits, last, modulus, name=""):
  118.     '''Check modulus against database'''
  119.     global db_lines
  120.     if last != bits:
  121.         db = db_prefix + bits
  122.         # Read in the database
  123.         try:
  124.             fh = open(db, 'r')
  125.         except:
  126.             try:
  127.                 print >> sys.stderr, "WARN: could not open database for %s " \
  128.                                      "bits. Skipped %s" % (bits, name)
  129.             except IOError:
  130.                 pass
  131.             return False
  132.  
  133.         db_lines = fh.read().split('\n')
  134.         fh.close()
  135.  
  136.     key = sha.sha(modulus).hexdigest()
  137.     #print "bits: %s\nmodulus: %s\nkey: %s\nkey80: %s" % (bits, modulus, key, key[20:])
  138.     if key[20:] in db_lines:
  139.         if not options.quiet:
  140.             print "COMPROMISED: %s %s" % (key, name)
  141.         return True
  142.     else:
  143.         if not options.quiet:
  144.             print "Not blacklisted: %s %s" % (key, name)
  145.         return False
  146.  
  147.  
  148. last_bits = ""
  149. found = False
  150.  
  151. if options.bits and options.modulus:
  152.     found = check_db(options.bits, last_bits, "Modulus=" + options.modulus + \
  153.                      "\n")
  154. else:
  155.     # Check each file
  156.     for f in args:
  157.         realname = f
  158.  
  159.         if f == "-":
  160.             # dump stdin to tmpfile, operate on tmpfile instead
  161.             temp = tempfile.NamedTemporaryFile()
  162.             shutil.copyfileobj(sys.stdin,temp)
  163.             temp.flush()
  164.             f = temp.name
  165.  
  166.         if not os.path.exists(f):
  167.             if not options.quiet:
  168.                 print >> sys.stderr, "'%s' could not be opened (skipping)" % \
  169.                                       (realname)
  170.             continue
  171.  
  172.         (type, contents) = get_contents(f)
  173.         if type == "":
  174.             if not options.quiet:
  175.                 print >> sys.stderr, "'%s' is not x509, req or rsa (skipping)" \
  176.                                        % (realname)
  177.             continue
  178.  
  179.         exp = get_exponent(contents)
  180.         if exp == "":
  181.             if not options.quiet:
  182.                 print >> sys.stderr, "Unsupported exponent '%s' (skipping)" % \
  183.                                       (realname)
  184.             continue
  185.  
  186.         bits = get_bits(contents, type)
  187.         if bits == "":
  188.             if not options.quiet:
  189.                 print >> sys.stderr, "Key has unknown validity: %s" % \
  190.                                       (realname)
  191.             continue
  192.  
  193.         modulus = get_modulus(contents)
  194.         if modulus == "":
  195.             if not options.quiet:
  196.                 print >> sys.stderr, "Problem finding modulus: %s" % (realname)
  197.             continue
  198.  
  199.         found = check_db(bits, last_bits, modulus, realname)
  200.         last_bits = bits
  201.  
  202. if found:
  203.     sys.exit(1)
  204.  
  205.